﻿#include "TlvThreadCom.h"

#include <QTimer>
#include <QEventLoop>
#include <QDebug>

TlvThreadCom::TlvThreadCom(QObject *parent)
    : QObject(parent)
    , m_thd(new QThread(this))
    , m_worker(nullptr)
{
    m_thd->start();
}

TlvThreadCom::~TlvThreadCom()
{
    if (m_worker) {
        unbindWorkerSignals(m_worker);
        m_worker->deleteLater();
    }
    if (m_thd->isRunning()) {
        m_thd->quit();
        m_thd->wait();
    }
}

bool TlvThreadCom::isOpen() const
{
    if (!m_worker) return false;
    // return m_worker->isOpen();
    bool opened = false;
    QMetaObject::invokeMethod(m_worker, [&](){ opened = m_worker->isOpen(); }, Qt::BlockingQueuedConnection);
    return opened;
}

QString TlvThreadCom::hwName() const
{
    if (!m_worker) return tr("No Device Registered");
    return m_worker->hwName();
}

void TlvThreadCom::setWorker(ICom *worker)
{
    if (!worker) return;

    if (worker == m_worker) return;

    if (m_worker) {
        // 清理旧的
        unbindWorkerSignals(m_worker);
        m_worker->close();
    }

    m_worker = worker;
    m_worker->moveToThread(m_thd);
    bindWorkerSignals(m_worker);
}

QList<Tlv> TlvThreadCom::syncWrite(const Tlv &tlv, uint16_t timeout)
{
    QList<Tlv> result;

    // 在 worker 所在子线程执行真正的阻塞等待
    QMetaObject::invokeMethod(m_worker, [&](){
    auto resultPtr = std::make_shared<QList<Tlv>>();
    QEventLoop loop;

    // 收集一帧内的 TLV
    auto conn1 = QObject::connect(m_worker, &ICom::dataReceived, m_worker,
        [resultPtr](const Tlv& recvTlv){
            resultPtr->append(recvTlv);
        });

        // 帧结束退出
        auto conn2 = QObject::connect(m_worker, &ICom::frameDone, &loop, &QEventLoop::quit);

        m_worker->write(tlv);

        // 超时退出
        QTimer::singleShot(timeout, &loop, [&](){
            emit errorOccurred(tr("Tlv Request Timeout!"));
            loop.quit();
        });

        loop.exec();

        QObject::disconnect(conn1);
        QObject::disconnect(conn2);

        result = *resultPtr;
    }, Qt::BlockingQueuedConnection);

    return result;
}

bool TlvThreadCom::syncWrite(const Tlv &inTlv, Tlv &outTlv, uint16_t timeout)
{
    auto res = syncWrite(inTlv, timeout);
    if (res.isEmpty()) {
        return false;
    } else {
        outTlv = res[0];
        return true;
    }
}

ICom *TlvThreadCom::worker()
{
    return m_worker;
}

TlvThreadCom *TlvThreadCom::instance()
{
    static TlvThreadCom* inst = nullptr;
    if (!inst) {
        inst = new TlvThreadCom();

        // 跨线程通讯必须注册：
        qRegisterMetaType<Tlv>("Tlv");
        qRegisterMetaType<Tlv>("Tlv&");
        qRegisterMetaType<Tlv>("const Tlv&");
    }
    return inst;
}

void TlvThreadCom::open()
{
    QMetaObject::invokeMethod(m_worker, "open", Qt::QueuedConnection);
}

void TlvThreadCom::close()
{
    QMetaObject::invokeMethod(m_worker, "close", Qt::QueuedConnection);
}

void TlvThreadCom::write(const Tlv &tlv)
{
    if (!m_worker) {
        emit errorOccurred(ICom::ERR_NOT_OPEN());
        return;
    }
    QMetaObject::invokeMethod(m_worker, "write", Qt::QueuedConnection, Q_ARG(Tlv, tlv));
}

void TlvThreadCom::bindWorkerSignals(ICom *worker)
{
    connect(worker, &ICom::frameDone, this, &TlvThreadCom::frameDone);
    connect(worker, &ICom::dataSend, this, &TlvThreadCom::dataSend);
    connect(worker, &ICom::dataReceived, this, &TlvThreadCom::dataReceived);
    connect(worker, &ICom::errorOccurred, this, &TlvThreadCom::errorOccurred);
    connect(worker, &ICom::opened, this, [=](){ emit opened(); emit statusChanged(true); });
    connect(worker, &ICom::closed, this, [=](){ emit closed(); emit statusChanged(false); });
}

void TlvThreadCom::unbindWorkerSignals(ICom *worker)
{
    // disconnect(worker, nullptr, this, nullptr);
    if (worker) worker->disconnect(this);
}
